Skip to main content

media_pp\elements\source/
rtsp_source.rs

1use std::{sync::Arc, time::Duration};
2
3use crate::pp_log::{PpLog, pp_error, pp_info};
4use ffmpeg_next as ffmpeg;
5use thiserror::Error as ThisError;
6
7use crate::{
8    buffer::MediaBuffer,
9    bus::{Bus, BusEvent},
10    control::{ControlReceiver, drain_control},
11    element::{Element, ElementType, Source, SourceElement, element_pp_log},
12    elements::RtspTransport,
13    error::Result,
14    pad::SrcPad,
15};
16
17use super::file_demuxer::StreamInfo;
18
19/// Errors specific to `RtspSource`. Converts into the crate-wide `Error`
20/// via `?` (see [`crate::error::Error`]).
21#[derive(Debug, ThisError)]
22pub enum RtspSourceError {
23    #[error("ffmpeg error: {0}")]
24    Ffmpeg(#[from] ffmpeg::Error),
25    #[error("RtspSource doesn't support seeking a live stream")]
26    SeekUnsupported,
27}
28
29/// Construction-time options for [`RtspSource::open`].
30#[derive(Debug, Clone, Copy)]
31pub struct RtspOptions {
32    pub transport: RtspTransport,
33    /// Socket I/O timeout — ffmpeg's own `timeout` RTSP demuxer option,
34    /// which covers the initial connect/handshake reads too, not just
35    /// steady-state ones. Without this, ffmpeg's own default is *no
36    /// timeout at all*, meaning [`RtspSource::open`] can hang forever
37    /// against an unreachable or dead server.
38    pub timeout: Duration,
39}
40
41impl Default for RtspOptions {
42    fn default() -> Self {
43        Self {
44            transport: RtspTransport::Tcp,
45            timeout: Duration::from_secs(5),
46        }
47    }
48}
49
50/// Demuxes a live RTSP stream — the client/receive counterpart to
51/// [`crate::elements::RtspSink`] (which publishes). One src pad per
52/// stream the server advertises, same shape as
53/// [`crate::elements::FileDemuxer`].
54///
55/// Deliberately does **not** retry or reconnect internally: a read failure
56/// (dropped connection, camera reboot, ...) ends this source's thread with
57/// `Err`, the same way any other fatal [`SourceElement::run`] failure
58/// does, instead of looping forever inside `run()`. Reconnecting means
59/// building a fresh `RtspSource`/[`crate::pipeline::Pipeline`] — mirrors
60/// `Pipeline` itself not being reusable once it ends: watch
61/// [`crate::pipeline::Pipeline::bus`], and on error, call
62/// [`RtspSource::open`] again.
63///
64/// Uses `Packet::read` directly instead of `Input::packets()` — the
65/// latter silently retries forever inside its own `next()` on any non-EOF
66/// error (network timeout, connection reset, ...), which would make a
67/// stuck connection un-`Stop`-able (`drain_control` never gets a turn)
68/// and this element's "fail fast, don't retry" contract impossible to
69/// keep.
70pub struct RtspSource {
71    pp_log: PpLog,
72    name: Arc<str>,
73    input: ffmpeg::format::context::Input,
74    pads: Vec<SrcPad>,
75}
76
77impl RtspSource {
78    /// Connects to `url` (e.g. `rtsp://host:port/path`) and returns the
79    /// element alongside every stream the server advertised, so the
80    /// caller can inspect them before deciding which of `src_pads()` to
81    /// link — same pattern as `FileDemuxer::open`.
82    pub fn open(
83        name: impl Into<String>,
84        url: impl AsRef<str>,
85        options: RtspOptions,
86    ) -> std::result::Result<(Self, Vec<StreamInfo>), RtspSourceError> {
87        let mut dict = ffmpeg::Dictionary::new();
88        dict.set("rtsp_transport", options.transport.as_ffmpeg_option());
89        dict.set("timeout", &options.timeout.as_micros().to_string());
90
91        let input = ffmpeg::format::input_with_dictionary(url.as_ref(), dict)?;
92
93        let streams: Vec<StreamInfo> = input
94            .streams()
95            .map(|s| StreamInfo {
96                index: s.index(),
97                kind: s.parameters().medium(),
98            })
99            .collect();
100
101        let pads = streams
102            .iter()
103            .map(|s| SrcPad::new(format!("src_{}", s.index)))
104            .collect();
105
106        let name: Arc<str> = name.into().into();
107        let pp_log = element_pp_log(ElementType::RtspSource, &name, None);
108        pp_info!(
109            pp_log: &pp_log,
110            "opened: url={}, transport={:?}, {} stream(s)",
111            url.as_ref(),
112            options.transport,
113            streams.len()
114        );
115        Ok((
116            Self {
117                name,
118                pp_log,
119                input,
120                pads,
121            },
122            streams,
123        ))
124    }
125
126    /// Codec parameters for one of this stream's streams — what you need
127    /// to construct a matching [`crate::elements::SwDecoder`] for it.
128    pub fn stream_parameters(&self, index: usize) -> Option<ffmpeg::codec::Parameters> {
129        self.stream(index).map(|s| s.parameters())
130    }
131
132    /// The unit decoded frame timestamps for this stream are expressed in
133    /// — what you need to construct a matching [`crate::elements::Pacer`]
134    /// for it.
135    pub fn stream_time_base(&self, index: usize) -> Option<ffmpeg::Rational> {
136        self.stream(index).map(|s| s.time_base())
137    }
138
139    fn stream(&self, index: usize) -> Option<ffmpeg::format::stream::Stream<'_>> {
140        self.input.streams().find(|s| s.index() == index)
141    }
142}
143
144impl Element for RtspSource {
145    fn name(&self) -> Arc<str> {
146        self.name.clone()
147    }
148
149    fn element_type(&self) -> ElementType {
150        ElementType::RtspSource
151    }
152
153    fn pp_log(&self) -> &PpLog {
154        &self.pp_log
155    }
156
157    fn pp_log_mut(&mut self) -> &mut PpLog {
158        &mut self.pp_log
159    }
160}
161
162impl Source for RtspSource {
163    fn src_pads(&mut self) -> &mut [SrcPad] {
164        &mut self.pads
165    }
166}
167
168impl SourceElement for RtspSource {
169    fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
170        pp_info!(self, "started");
171        loop {
172            if drain_control(control, self, bus)?.stopped {
173                pp_info!(self, "stopped");
174                return Ok(());
175            }
176
177            let mut packet = ffmpeg::Packet::empty();
178            match packet.read(&mut self.input) {
179                Ok(()) => {
180                    let index = packet.stream();
181                    if let Some(pad) = self.pads.get_mut(index) {
182                        // A downstream failure drops just this one packet
183                        // — same "report, don't die" contract `Queue`'s
184                        // worker gives a failing `Sink` — rather than
185                        // ending this whole source thread over it.
186                        if let Err(error) = pad.push(MediaBuffer::Packet(Arc::new(packet))) {
187                            bus.post(
188                                &self.pp_log,
189                                BusEvent::Error {
190                                    element_type: ElementType::RtspSource,
191                                    name: self.name.clone(),
192                                    error,
193                                },
194                            );
195                        }
196                    }
197                }
198                // A real on-demand RTSP stream can send a clean EOF; a
199                // live camera essentially never will, but treat it the
200                // same way `FileDemuxer` treats running out of packets.
201                Err(ffmpeg::Error::Eof) => break,
202                // Anything else (connection reset, socket timeout, ...) is
203                // fatal — reported and this thread ends, rather than
204                // retried. See this type's own docs on why: retrying
205                // belongs to whoever's watching the bus, building a fresh
206                // `RtspSource` to reconnect with.
207                Err(error) => {
208                    pp_error!(self, "read failed: {error}");
209                    return Err(RtspSourceError::Ffmpeg(error).into());
210                }
211            }
212        }
213        for pad in self.pads.iter_mut() {
214            pad.push_eos(&self.pp_log)?;
215        }
216        pp_info!(self, "event=eos phase=source_completed outcome=ok");
217        Ok(())
218    }
219
220    fn seek(&mut self, _target: Duration) -> Result<Duration> {
221        Err(RtspSourceError::SeekUnsupported.into())
222    }
223}